import numpy as np
import pandas as pd
from typing import List, Optional
from sklearn.feature_selection import SelectKBest, f_regression

class FeatureEngineer:
    def __init__(self):
        pass
        
    def create_time_features(self, df: pd.DataFrame,
                            datetime_column: str) -> pd.DataFrame:
        """Create time-based features from datetime column
        
        Args:
            df (pd.DataFrame): Input dataframe
            datetime_column (str): Name of datetime column
            
        Returns:
            pd.DataFrame: Dataframe with additional time features
        """
        df = df.copy()
        df[datetime_column] = pd.to_datetime(df[datetime_column])
        
        # Extract time components
        df['hour'] = df[datetime_column].dt.hour
        df['day'] = df[datetime_column].dt.day
        df['month'] = df[datetime_column].dt.month
        df['year'] = df[datetime_column].dt.year
        df['dayofweek'] = df[datetime_column].dt.dayofweek
        df['quarter'] = df[datetime_column].dt.quarter
        
        # Create cyclical features
        df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
        df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
        df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
        df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
        
        return df
    
    def create_lag_features(self, df: pd.DataFrame,
                           columns: List[str],
                           lag_periods: List[int]) -> pd.DataFrame:
        """Create lag features for specified columns
        
        Args:
            df (pd.DataFrame): Input dataframe
            columns (List[str]): Columns to create lag features for
            lag_periods (List[int]): List of lag periods
            
        Returns:
            pd.DataFrame: Dataframe with lag features
        """
        df = df.copy()
        
        for col in columns:
            for lag in lag_periods:
                df[f'{col}_lag_{lag}'] = df[col].shift(lag)
                
        return df
    
    def create_rolling_features(self, df: pd.DataFrame,
                               columns: List[str],
                               windows: List[int]) -> pd.DataFrame:
        """Create rolling window features
        
        Args:
            df (pd.DataFrame): Input dataframe
            columns (List[str]): Columns to create rolling features for
            windows (List[int]): List of window sizes
            
        Returns:
            pd.DataFrame: Dataframe with rolling features
        """
        df = df.copy()
        
        for col in columns:
            for window in windows:
                df[f'{col}_rolling_mean_{window}'] = df[col].rolling(window=window).mean()
                df[f'{col}_rolling_std_{window}'] = df[col].rolling(window=window).std()
                df[f'{col}_rolling_min_{window}'] = df[col].rolling(window=window).min()
                df[f'{col}_rolling_max_{window}'] = df[col].rolling(window=window).max()
                
        return df
    
    def select_features(self, X: np.ndarray,
                       y: np.ndarray,
                       k: Optional[int] = None) -> np.ndarray:
        """Select top k features based on correlation with target
        
        Args:
            X (np.ndarray): Feature matrix
            y (np.ndarray): Target vector
            k (Optional[int]): Number of features to select
            
        Returns:
            np.ndarray: Selected features
        """
        if k is None:
            k = X.shape[1] // 2
            
        selector = SelectKBest(score_func=f_regression, k=k)
        X_selected = selector.fit_transform(X, y)
        
        return X_selected